import serial
import os

# 2021-08-31 14:50 by 炸天帮成员
# 修改自 demo1文件夹中的main.py
# 整合各个模块为函数，专为搭载MicroPython的ESP8266做了适配，并保留通用型UART函数。

def findSerialPort()->str:
    """
        2021-08-31  13:31   by  炸天帮成员
        函数名：
            findSerialPort()
        函数用途：
            用于查询电脑端可用的串口。
        类型：
            通用型函数。
    :return:    返回一个字符串，内容为有挂载的串口号。
    """
    serial_ports = os.popen('python -m serial.tools.list_ports').read().strip()  # 调用控制台并读取cmd命令行的结果
    print('serial_ports = ', serial_ports,type(serial_ports))  # 显示当前有效的COM端
    return serial_ports
# ----------------------------------------------------------------
# 以下为常规serial_Port的值
# Default=(port=None,                     – Device name or None.
#               baudrate=9600,(int)         – Baud rate such as 9600 or 115200 etc.
#               bytesize=EIGHTBITS,        – Number of data bits. Possible values: FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
#               parity=PARITY_NONE,     – Enable parity checking. Possible values: PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, PARITY_SPACE
#               stopbits=STOPBITS_ONE,– Number of stop bits. Possible values: STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO
#               timeout=None,(float)       – Set a read timeout value.
#               xonxoff=False,(bool)        – Enable software flow control.
#               rtscts=False,(bool)           – Enable hardware (RTS/CTS) flow control.
#               write_timeout=None,(float) – Set a write timeout value.
#               dsrdtr=False,                   – Enable hardware (DSR/DTR) flow control.
#               inter_byte_timeout=None(float) – Inter-character timeout, None to disable (default).)
# ---------------------------------------------------------------
def serial_Port_Init(serial_ports:str)->serial.serialwin32.Serial:
    """
        2021-08-31  13:33   by  炸天帮成员
        函数名：
            serial_Port_Init()
        函数用途：
            用于设置UART的通讯参数。
        类型：
            通用型函数。
    :param serial_ports:    为一个有挂载的串口端口号，一般来自findSerialPort()。
    :return:                serial.serialwin32.Serial类型的对象。
    """
    ser = serial.Serial()       # 实例化serial_com端口
    ser.port = serial_ports     # 设置端口号
    ser.baudrate = 115200       # 设置端口的波特率115200
    ser.bytesize = serial.EIGHTBITS
    ser.parity = serial.PARITY_NONE
    ser.stopbits = serial.STOPBITS_ONE
    ser.timeout = 0.01          # 设定读取超时时间0.01s
    ser.xonxoff = False
    ser.rtscts = False
    ser.write_timeout = None    # Set a write timeout value.
    ser.dsrdtr = False
    ser.inter_byte_timeout = None
    print("ser = ",ser,type(ser))  # 输出当前端口的详细信息
    return ser


# ----------------------------------------------------------------
# 字节串（bytes）和字符串（string）的对比：
# 字符串由若干个字符组成，以字符为单位进行操作；字节串由若干个字节组成，以字节为单位进行操作。
# 字节串和字符串除了操作的数据单元不同之外，它们支持的所有方法都基本相同。
# 字节串和字符串都是不可变序列，不能随意增加和删除数据。

# bytes 只负责以字节序列的形式（二进制形式）来存储数据，至于这些数据到底表示什么内容（字符串、数字、图片、音频等），完全由程序的解析方式决定。如果采用合适的字符编码方式（字符集），字节串可以恢复成字符串；反之亦然，字符串也可以转换成字节串。

# 说白了，bytes 只是简单地记录内存中的原始数据，至于如何使用这些数据，bytes 并不在意，你想怎么使用就怎么使用，bytes 并不约束你的行为。

# bytes 类型的数据非常适合在互联网上传输，可以用于网络通信编程；bytes 也可以用来存储图片、音频、视频等二进制格式的文件。

# 字符串和 bytes 存在着千丝万缕的联系，我们可以通过字符串来创建 bytes 对象，或者说将字符串转换成 bytes 对象。有以下三种方法可以达到这个目的：
# 如果字符串的内容都是 ASCII 字符，那么直接在字符串前面添加b前缀就可以转换成 bytes。
# bytes 是一个类，调用它的构造方法，也就是 bytes()，可以将字符串按照指定的字符集转换成 bytes；如果不指定字符集，那么默认采用 UTF-8。
# 字符串本身有一个 encode() 方法，该方法专门用来将字符串按照指定的字符集转换成对应的字节串；如果不指定字符集，那么默认采用 UTF-8。
# ------------------------------------------------------------------
# 以下为读写serial_Port，串口的发送内容必须要为bytes类型字符串


def readBytesFromUART(targetSerial:serial.serialwin32.Serial)->str:
    """
        2021-08-31  13:35   by  炸天帮成员
        函数名：
            readBytesFromUART()
        函数用途：
            用于获取来自UART返回值。
        类型：
            单次通用型函数。
    :param targetSerial:    一般为下位机所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param repeatTime:      xxx.read()重复扫描的次数，间隔时间在serial_Port_Init()中的timeout中设置。可以在一定程度上解决EOF遇到0x0d0x0a后的问题。
    :return:    来自UART的字符串结果。
    """
    data_return = targetSerial.read()   # 可以按字节数读取来自下位机的串口内容，此处设定为100字节
    print('data_return = ', data_return)  # 显示从COM端读取到的内容
    data_in_waiting = targetSerial.read(targetSerial.in_waiting)
    print('ser.in_waiting =', data_in_waiting)
    result = data_return + data_in_waiting
    print('uart结果为：', result.decode(encoding="UTF-8"))
    return result

def sendStringToUART(targetSerial:serial.serialwin32.Serial,theContent: str):
    """
        2021-08-31  13:41   BY  炸天帮成员
        函数名：
            sendStringToUART()
        函数用途：
            用于向MCU中传入Bytes字节流，并得到其返回值。
        函数类型：
            无读取功能的通用型函数。
            后续可以加一个readBytesFromUART()。
    :param targetSerial:一般为MCU所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param theContent:  用于向MCU中传入Bytes字节流所对应的字符串内容。
    :return:            无。
    """
    send_bytes = bytes(theContent, encoding='UTF-8') + b'\x0d\x0a'
    send_string = send_bytes.decode('UTF-8')
    print('send_bytes = ', send_bytes, 'send_bytes 的类型为：', type(send_bytes), '\nsend_string = ', send_string,
          ' send_string 的类型为', type(send_string))
    targetSerial.write(send_bytes)  # 发送数据至MCU

def MCUpyUARTConnect(targetSerial:serial.serialwin32.Serial):
    """
        2021-08-31  14:04   by  炸天帮成员
        函数名：
            MCUpyUARTConnect()
        函数说明：
            使用通用型函数适配ESP8266芯片。
        函数类型：
            修改[0]中的所有内容，即可成为其余芯片UART的代码。
    :param targetSerial:    serial.serialwin32.Serial对象，一般由serial_Port_Init()获得。
    :return:                无。
    """
    targetSerial.open()
    if targetSerial.is_open:
        #[0]通用型函数适配ESP8266芯片，进行UART数据的读写。
        sendStringToUART(targetSerial=targetSerial,theContent = "import machine")
        readBytesFromUART(targetSerial=targetSerial)
        readBytesFromUART(targetSerial=targetSerial)

        sendStringToUART(targetSerial=targetSerial,theContent = "machine.freq()")
        readBytesFromUART(targetSerial=targetSerial)
        readBytesFromUART(targetSerial=targetSerial)
        #其他写读UART数据的操作。
    targetSerial.close()


def readBytesFromESP8266(targetSerial:serial.serialwin32.Serial,repeatTime:int=10):
    """
        2021-08-31  13:35   by  炸天帮成员
        函数名：
            readBytesFromESP8266()
        函数用途：
            用于获取来自ESP8266的UART返回值。在通用型的基础上做了去除空byte流的操作。
            通过修改repeatTime，可以对ESP8266的返回内容做出条数删选。
        函数类型：
            MicroPython特化函数。
            将if和for去除后，即为单次通用型函数readBytesFromUART()。
    :param targetSerial:    一般为ESP8266所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param repeatTime:  xxx.read()重复扫描的次数，间隔时间在serial_Port_Init()中的timeout中设置。可以在一定程度上解决EOF遇到0x0d0x0a后的问题。
    :return:
    """
    for looptime in range(0,repeatTime):
        data_return = targetSerial.read()  # 可以按字节数读取来自下位机的串口内容，此处设定为100字节
        if data_return != b'':
            #print('data_return = ', data_return)  # 显示从COM端读取到的内容
            data_in_waiting = targetSerial.read(targetSerial.in_waiting)
            #print('ser.in_waiting =', data_in_waiting)
            result = data_return + data_in_waiting
            print('esp8266返回：', result.decode(encoding="UTF-8"))

def sendStringToESP8266(targetSerial:serial.serialwin32.Serial,theContent: str):
    """
        2021-08-31  13:41   BY  炸天帮成员
        函数名：
            sendStringToESP8266()
        函数用途：
            用于向ESP8266中传入Bytes字节流，并得到其返回值。
        函数类型：
            MicroPython特化函数。
            去除readBytesFromESP8266()后，为普通无读取功能的通用型函数。
    :param targetSerial:一般为ESP8266所在的端口号所对应的serial.serialwin32.Serial类型的对象。一般来自serial_Port_Init()。
    :param theContent:  用于向ESP8266中传入Bytes字节流所对应的字符串内容。
    :return:
    """
    send_bytes = bytes(theContent, encoding='UTF-8') + b'\x0d\x0a'
    send_string = send_bytes.decode('UTF-8')
    #print('send_bytes = ', send_bytes, 'send_bytes 的类型为：', type(send_bytes), '\nsend_string = ', send_string,' send_string 的类型为', type(send_string))
    targetSerial.write(send_bytes)                  # 发送数据至ESP8266。
    readBytesFromESP8266(targetSerial=targetSerial) # 读取来自ESP8266的数据。

def ESP8266pyUARTConnect(targetSerial:serial.serialwin32.Serial):
    """
        函数名：
            ESP8266pyUARTConnect()
        函数说明：
            使用ESP8266特化型的函数，从而来实现和MCUpyUARTConnect()相类似的功能。
    :param targetSerial:    serial.serialwin32.Serial对象，一般由serial_Port_Init()获得。
    :return:
    """
    targetSerial.open()  # 开启ESP8266端口
    if targetSerial.is_open:

        sendStringToESP8266(targetSerial=targetSerial,theContent="import machine")
        sendStringToESP8266(targetSerial=targetSerial,theContent="machine.freq()")

    targetSerial.close()  # 关闭ESP8266端口


if __name__=="__main__":
    ESP8266isWhere=findSerialPort()                     #   获取ESP8266所在的串口端口号。
    ESP8266Serial = serial_Port_Init(ESP8266isWhere)    #   将ESP8266的串口端口号实例成serial.serialwin32.Serial对象，从而进行程序操作。

    # MCUpyUARTConnect(ESP8266Serial)                   #   使用通用型UART函数适配ESP8266
    ESP8266pyUARTConnect(ESP8266Serial)                 #   使用特化型的ESP8266函数来实现MCUpyUARTConnect()相同的功能。
